#include "CBullet.h"
#include "MainLayer.h"
#include "GamePlayScene.h"
USING_NS_CC;

/**
 * This class finds objects overlapping a circle area indicated by "b2CircleShape circle" class member.
 * It is used in Bullet::collision() method as parameter for QueryAABB();
 */
class ExplosionCallback : public b2QueryCallback
{
public:
	/**
	 * The are that should be tested for overlapping objects.
	 * Set this before calling the QueryAABB() method.
	 */
	b2CircleShape 	circle;

	/**
	 * Used by the b2TestOverlap() method. Can be set using transform.SetIdentity().
	 */
	b2Transform 	transform;

	/**
	 * The base damage value. It will be reduced according to distance form the center of the circle.
	 */
	float 			damage;

	/**
	 * This method finds the overlapping objects and calls collision(float damage) method on them.
	 * @see b2QueryCallback
	 */
	bool ReportFixture(b2Fixture* fixture)
	{
		b2Body* body = fixture->GetBody();
		b2Shape* shape = fixture->GetShape();
		CAnimatedObject* animatedObject = static_cast<CAnimatedObject*>(body->GetUserData());
		if(animatedObject->getTag() == MainLayer::enemyTag)
		{
			bool overlap = b2TestOverlap(shape, 0, &circle, 0, body->GetTransform(), transform);
			if (overlap)
			{
				float distMagnitude = 0.1f;
				float distance = b2Distance(circle.m_p, body->GetPosition());
				float explosionDmg = damage / (1 + distance * distMagnitude);

				animatedObject->collision(explosionDmg);
			}
		}

		return true;
	}
};

CBullet::CBullet():
	_bulletParams(),
	_winSize()
{
}

/**
 * @return true if the sprite was initialized without problems, false otherwise.
 */
bool
	CBullet::init(char* frameNamePrefix, cocos2d::CCSpriteBatchNode* pSpriteSheet, b2World *pWorld)
{
	if(!CAnimatedObject::init(frameNamePrefix, pSpriteSheet, pWorld))
	{
		return false;
	}

	_winSize = CCDirector::sharedDirector()->getWinSize();

	this->setTag(MainLayer::bulletTag);
	createBoxBody();

	return true;
}

/**
 * Apply force to the bullet based on bulletParams.
 */
void
CBullet::fire(CBulletParams bulletParams)
{
	_bulletParams = bulletParams;
	switch(_bulletParams._type)
	{
	case CBulletParams::standard:
		{
			float forceLevel = _bulletParams._speed * _pBody->GetMass();
			_bulletParams._startingPosition.x += 20.0f / P2M;
			_bulletParams._startingPosition.y -= 7.0f / P2M;
			_pBody->SetTransform(_bulletParams._startingPosition, 0.0f);
			b2Vec2 force(forceLevel, 0.0f);
			_pBody->ApplyForceToCenter(force);
		}
		break;

	case CBulletParams::bomb:
		{
			float forceLevel = _bulletParams._speed / 2.0f * _pBody->GetMass();
			_bulletParams._startingPosition.x += 2.0f / P2M;
			_bulletParams._startingPosition.y += 20.0f / P2M;
			_pBody->SetTransform(_bulletParams._startingPosition, M_PI / 4.0);
			b2Vec2 force(forceLevel, forceLevel + 6.0f);
			_pBody->ApplyForceToCenter(force);
			_pBody->ApplyTorque(-0.2f);
		}
		break;
	}

	this->runAction(CCSequence::actions(CCDelayTime::actionWithDuration(1.5f),
										CCCallFunc::actionWithTarget(this, callfunc_selector(CBullet::removeBullet)),
										NULL));
}

/**
 * The object will be removed by the Ship in cleanBullets() method
 * called indirectly by the MainLayer in update() method.
 */
void
CBullet::removeBullet()
{
	this->setDelete(true);
}

float
CBullet::getDamage()
{
	return _bulletParams._damage;
}

void
CBullet::createBoxBody()
{
    b2BodyDef spriteBodyDef;
    spriteBodyDef.type = b2_dynamicBody;
    spriteBodyDef.active = true;
    float x = this->getPosition().x / P2M;
    float y = this->getPosition().y / P2M;
    spriteBodyDef.position.Set(x, y);
    spriteBodyDef.userData = this;
    _pBody = _pWorld->CreateBody(&spriteBodyDef);

    b2PolygonShape spriteShape;
    int num = 6;
    b2Vec2 verts[] = {
    		b2Vec2(1.5055f / P2M, -4.2085f / P2M),
    		b2Vec2(8.4504f / P2M, -1.9473f / P2M),
    		b2Vec2(9.4193f / P2M, 2.2518f / P2M),
    		b2Vec2(6.3506f / P2M, 4.8358f / P2M),
    		b2Vec2(-0.2710f / P2M, 5.4819f / P2M),
    		b2Vec2(-14.4835f / P2M, 0.1522f / P2M)
   };

    spriteShape.Set(verts, num);

    b2FixtureDef spriteShapeDef;
    spriteShapeDef.shape = &spriteShape;
    spriteShapeDef.density = 0.3f;
    spriteShapeDef.restitution = 0.1f;
    spriteShapeDef.filter.categoryBits = ECategoryBit::bulletCategoryBit;
    spriteShapeDef.filter.maskBits = ECategoryBit::enemyCategoryBit;

    _pBody->CreateFixture(&spriteShapeDef);
}

void
CBullet::collision()
{
	switch(_bulletParams._type)
	{
	case CBulletParams::standard:
		// The standard bullet will hit (bounce off) enemies a few time before it is deleted.
		_bulletParams._hits--;
		if(_bulletParams._hits <= 0)
		{
			this->setDelete(true);
		}
		break;

	case CBulletParams::bomb:
		// The bomb bullet will fly along an arc and explode on contact with an enemy.
		b2AABB explosionRange;
		float radius = 200;
		float x1 = _pBody->GetPosition().x - radius / P2M;
		float y1 = _pBody->GetPosition().y - radius / P2M;
		float x2 = _pBody->GetPosition().x + radius / P2M;
		float y2 = _pBody->GetPosition().y + radius / P2M;
		explosionRange.lowerBound.Set(x1, y1);
		explosionRange.upperBound.Set(x2, y2);

		ExplosionCallback explosion;
		explosion.circle.m_radius = radius / 2.0f / P2M;
		explosion.circle.m_p.Set(_pBody->GetPosition().x, _pBody->GetPosition().y);
		explosion.transform.SetIdentity();
		explosion.damage = 500.0f;
		_pWorld->QueryAABB(&explosion, explosionRange); // Find objects in the range of explosion and apply damage.

		this->setDelete(true);

		break;
	}
}

CBulletParams::EBulletType
CBullet::getType()
{
	return _bulletParams._type;
}
